/******************************************************************************\
*                                                                              *
*     File: TestSet.c                                                          *
*                                                                              *
*   Author: Douwe A. van Twillert                                              *
*  Purpose: Defines hooks to generate output for the TestSet test framework    *
*  Version: 1.0      TWI 20070311                                              *
*           - Initial version                                                  *
*  Version 1.1:      TWI 20070315                                              *
*           - Added expected signal handling                                   *
*  Version: 1.1.1    AKK 20070322                                              *
*           - Added psignal in TestSet_handle_signal() to name the             *
*             (unexpected) signal                                              *
*           + Alternatives are: strsignal or sys_siglist[]                     *
*           + Voor Dev-Cpp is een extra 'extern' nodig omdat psignal           *
*           + daar eigenlijk niet bestaat                                      *
*  Version: 1.1.2    TWI 20070323                                              *
*           - Changed all tabs to spaces                                       *
*           - Made versioning comments consistent                              *
*  Version: 1.2      TWI 20080218                                              *
*           - Changed the header file for clear printing                       *
*           - Added a flush before each function of the other stream           *
*     TODO: make it C compatible again while retaining C++ compatibility       *
*                                                                              *
\******************************************************************************/


/*
 *  System Includes
 */
#include <stdio.h>
#include <setjmp.h>
#include <signal.h>
#include <stdlib.h>	// AKK 20090304 - Nodig voor 'exit()'

#ifndef    unix    /* AKK - 20070322 voor DevCpp */
extern    void psignal( int sig, const char * mesg );
#endif

/*
 *  Module includes
 */
#include "trace.h"
#include "TestSet.h"
#include "TestSet_output.h"

/*
 *  Module defines
 */

#ifndef __PRETTY_FUNC__
    #define __PRETTY_FUNC__ "TestSet"
#endif

#define TESTSET_ENSURE(condition)                                            \
            if (!(condition))                                                \
            {                                                                \
                TestSet_output_ensure(__FILE__, __LINE__,                    \
                                      __PRETTY_FUNC__, #condition);          \
                exit(__LINE__);                                              \
            }

#define TESTSET_MAGIC(testset)                                               \
            if (testset->magic != TEST_SET_MAGIC)                            \
            {                                                                \
                TestSet_output_magic(__FILE__, __LINE__,   __PRETTY_FUNC__,  \
               "TestSet", testset->magic, TEST_SET_MAGIC );                  \
                exit(__LINE__);                                              \
            }

#define TESTCASE_MAGIC(testcase)                                             \
            if (testcase->magic != TEST_CASE_MAGIC)                          \
            {                                                                \
                TestSet_output_magic(__FILE__, __LINE__,   __PRETTY_FUNC__,  \
                "TestSet", testcase->magic, TEST_CASE_MAGIC );               \
                exit(__LINE__);                                              \
            }


/*
 *  Module type definitions
 */
#ifndef __cplusplus
    typedef int bool;
#endif
   
/*
 *  Module global data
 */

/*
 *  Module local data
 */
static int passed = 0;
static int failed = 0;
static int total = 0;
static int nr_of_test_sets = 0;

static jmp_buf environment_buffer;

static TestSet default_test_set =
      { TEST_SET_MAGIC, "default", 0, 0, 0, 0, NULL, NULL, NULL, NULL };
static TestSet* current_test_set = &default_test_set;
static TestSet destructed_test_set =
      { TEST_SET_DESTRUCTED, "", -1, -1, -1, -1, NULL, NULL, NULL, NULL };

static int expected_signal = 0;
static int caught_signal = 0;


/*
 *  Module local functions
 */
 
/*#----------------------------------------------------------------------#*/
static void TestSet_execute_test(void)

/*
 *  Description : Execute the current test case
 *
 *  Input(s)    : -
 *  Output(s)   : -
 *  Returns     : -
 *  Notes       : Uses the for this file globally available variables:
 *                - test_set            to access the currect test case
 *                - expected signal     to ease building test cases which
 *                                      generate a signal
 *                - environment buffer  the signal jump environment buffer
 *
 *  Pre.Cond.   : signal handler should be installed (not checked!)
 *  Post.Cond.  : the current test cases has be executed and the statistics
 *                have beed updated according to the result
 */

/*#----------------------------------------------------------------------#*/
{
    TRACE_FUNC;

    TESTSET_ENSURE(current_test_set != 0);
    TESTSET_MAGIC(current_test_set);
    TESTSET_ENSURE(current_test_set->current != 0 );
    TESTCASE_MAGIC(current_test_set->current);
    TESTSET_ENSURE(current_test_set->current->test != 0 );
 
    bool result = !OK;
    const char* test_case_name = current_test_set->current->name ?
                                 current_test_set->current->name : "<no name>";

    TestSet_output_test_case_before(test_case_name);

    try {
        expected_signal = caught_signal = 0;
        TRACE("before setjump");
        if (setjmp(environment_buffer) == 0)
        {
            TRACE("after setjump=0");
            result = current_test_set->current->test();

            //  if expected_signal was set, it should have be caught
            if (expected_signal != 0)
               result = !OK;
        }
        else
        {
            TRACE("Result="<< result);
            TRACE("expected signal = " << expected_signal );
            TRACE("caught signal = " << caught_signal );
            //  in case of no signals, they both will be zero
            if ((expected_signal == caught_signal))
               result = OK;
        }
    }
    catch (...)
    {
        if ((expected_signal == SIGABRT))
           result = OK;
        else {
            TestCase* test_case = current_test_set ? current_test_set->current
                                          ? current_test_set->current : 0
                                          : 0;
            const char* test_case_name = test_case ? test_case ->name : "<no name>";
            TestSet_output_unhandled_exception( test_case_name );
            TRACE("Result="<< result);
            TRACE("expected signal = " << expected_signal );
            TRACE("caught signal = " << caught_signal );
            //  in case of no signals, they both will be zero
        }
    }

    TestSet_output_test_case_after(test_case_name, result);

    current_test_set->passed += (result == OK);
    current_test_set->failed += (result != OK);
    current_test_set->total++;
}

/*#----------------------------------------------------------------------#*/
static void TestSet_execute_test_set_tests(void)

/*
 *  Description : Executes the test cases
 *
 *  Input(s)    : -
 *  Output(s)   : -
 *  Returns     : -
 *  Notes       : Uses the for this file globally available test_set
 *  Pre.Cond.   : signal handler should be installed (not checked!)
 *  Post.Cond.  : all test cases should be executed.
 */

/*#----------------------------------------------------------------------#*/
{   
    TRACE_FUNC;

    TESTSET_ENSURE(current_test_set != 0);
    TESTSET_MAGIC(current_test_set);
    TESTSET_ENSURE(current_test_set->current != 0);
    TESTCASE_MAGIC(current_test_set->current);
    TESTSET_ENSURE(current_test_set->current->test != 0);

    while (current_test_set->current != NULL)
    {
        TESTCASE_MAGIC(current_test_set->current);
        TESTSET_ENSURE(current_test_set->current->test != 0);

        TestSet_execute_test();

        /*
         *  Make sure tjhat after a test case the memory structure is in
         */
        TESTSET_ENSURE(current_test_set != 0);
        TESTSET_MAGIC(current_test_set);
        TESTSET_ENSURE(current_test_set->current != 0);
        TESTCASE_MAGIC(current_test_set->current);
        TESTSET_ENSURE(current_test_set->current->test != 0);

        current_test_set->current = current_test_set->current->next;
    }
}



/*#----------------------------------------------------------------------#*/
static void TestSet_handle_signal(int a_signal)

/*-
 *  Description : Catches and handles signals if necessary
 *
 *  Input(s)    : signal_nr: signal which causes this handler to be invoked
 *  Output(s)   : -
 *  Returns     : -
 *  Notes       : -
 *  Pre.Cond.   : -
 *  Post.Cond.  : -
 *
 */

/*#----------------------------------------------------------------------#*/
{
    TRACE_FUNC;

    if ( expected_signal != a_signal ) {
        (void) fprintf(stderr, "\n\tSIGNAL CATCHED(%d)\n", a_signal );
        // AKK 20070322 - added
    psignal( a_signal, "\tMNEMONIC IS " );        // op stderr
        // AKK - END
        (void) fflush(stderr);
    }

    /* Checks to verify that the test set memory is not corrupted */
    TESTSET_ENSURE(current_test_set != 0);
    TESTSET_MAGIC(current_test_set);
    if (current_test_set->current != NULL )
    {
        TESTSET_ENSURE(current_test_set->current != 0);
        TESTCASE_MAGIC(current_test_set->current);
        TESTSET_ENSURE(current_test_set->current->test != 0);

        (void)signal(a_signal, TestSet_handle_signal);
        caught_signal = a_signal;
    }

    TRACE("before longjump");
    longjmp(environment_buffer, 0);
    TRACE("after longjump");

    exit(a_signal);
}


/*#----------------------------------------------------------------------#*/
static void TestSet_install_handlers(void)

/*-
 *  Description : Installs the signal handlers for the required signals
 *
 *  Input(s)    : -
 *  Output(s)   : -
 *  Returns     : -
 *  Notes       : SIGKILL can't be caught (and that is also not usefull
 *                because run away tests must remain killable)
 *  Pre.Cond.   : -
 *  Post.Cond.  : -
 */

/*#----------------------------------------------------------------------#*/
{
    TRACE_FUNC;
    int catched_signals[] = {
        SIGABRT,
        SIGFPE,
        SIGILL,
        SIGSEGV,
        SIGILL,   // SIGBUS,
        SIGTERM,
        NSIG //SIGKILL  // Not catched, last resourt when test fails recursively
    };
    int index = 0;
   
    /* now we can reset the signal handler, execute the next test case */
    while (catched_signals[index] != NSIG ) //SIGKILL )
    {
        (void)signal(catched_signals[index], TestSet_handle_signal);
        index++;
    }
}


/*#----------------------------------------------------------------------#*/
static void TestSet_execute_test_set()

/*-
 *  Description : Execute all test cases belonging to the test set
 *
 *  Input(s)    : -
 *  Output(s)   : -
 *  Returns     : -
 *  Notes       : Te test set itself contains the administration like
 *                the number of executed test cases, failed and passed etc.
 *  Pre.Cond.   : -
 *  Post.Cond.  : -
 */

/*#----------------------------------------------------------------------#*/
{
    TRACE_FUNC;

    TESTSET_ENSURE(current_test_set != 0);
    TESTSET_MAGIC(current_test_set);

    if ( current_test_set->nr_of_testcases < 1 ) {

        // the default test set can be ignored silently
        if ( current_test_set != &default_test_set )
        {
            const char* test_set_name = current_test_set->name ?
                                        current_test_set->name : "<noname>";

            TestSet_output_test_set_before(test_set_name,
                                           current_test_set->nr_of_testcases);
        }
    }
    else
    {
        const char* test_set_name = current_test_set->name ?
                               current_test_set->name : "<noname>";
        TestSet_output_test_set_before(test_set_name,
                                       current_test_set->nr_of_testcases);
        current_test_set->current = current_test_set->head;

        // use setjmp to jump back here if anything fails
        TestSet_execute_test_set_tests();
        
        TestSet_output_test_set_after(test_set_name,
                                      current_test_set->passed,
                                      current_test_set->failed,
                                      current_test_set->total);
        nr_of_test_sets++;
    }

    passed += current_test_set->passed;
    failed += current_test_set->failed;
    total += current_test_set->total;
}


/*
 *  Module Exported functions
 */
 
/*#----------------------------------------------------------------------#*/
extern TestSet* TestSet_add_set( TestSet* a_test_set )

/*
 *  Description : Adds a new test set to the test sets
 *
 *  Input(s)    : a_test_set - the test set to be added
 *  Output(s)   : -
 *  Returns     : -
 *  Notes       : Uses the for this file globally available current_test_set
 *  Pre.Cond.   : 
 *  Post.Cond.  : 
 */

/*#----------------------------------------------------------------------#*/
{   
    TRACE_FUNC;

    /* Checks to verify that the test set memory is not corrupted */
    TESTSET_ENSURE(current_test_set != 0);
    TESTSET_ENSURE(a_test_set != 0);
    TESTSET_MAGIC(current_test_set);
    TESTSET_MAGIC(a_test_set);
    TESTSET_ENSURE(current_test_set != a_test_set);

    current_test_set->next = a_test_set;  // default is the start
    current_test_set = a_test_set;
    nr_of_test_sets++;

    return 0;
}


/*#----------------------------------------------------------------------#*/
extern TestCase* TestSet_add_test_case( TestCase* a_test_case )

/*
 *  Description : Adds a test case to the current test set
 *
 *  Input(s)    : a_test_case - the test case to be added
 *  Output(s)   : -
 *  Returns     : -
 *  Notes       : Uses the for this file globally available current_test_set
 *  Pre.Cond.   : -
 *  Post.Cond.  : -
 */

/*#----------------------------------------------------------------------#*/
{
    TRACE_FUNC;

    /* Checks to verify that the test set memory is not corrupted */
    TESTSET_ENSURE(current_test_set != 0);
    TESTSET_MAGIC(current_test_set);
    TESTSET_ENSURE(a_test_case != 0);
    TESTCASE_MAGIC(a_test_case);

    if (current_test_set->tail == NULL)
    {
        current_test_set->head = current_test_set->tail = a_test_case;
    }
    else
    {
        current_test_set->tail->next = a_test_case;
        current_test_set->tail = a_test_case;
    }
    current_test_set->nr_of_testcases++;
    
    return NULL;
}


/*#----------------------------------------------------------------------#*/
 void TestSet_expect_signal(int a_signal)

/*-
 *  Description : Catches and handles signals if necessary
 *
 *  Input(s)    : a_signal: signal number to be generated by the test case
 *  Output(s)   : -
 *  Returns     : -
 *  Notes       : -
 *  Pre.Cond.   : the signal may not already have been set
 *  Post.Cond.  : the signal has been set
 */

/*#----------------------------------------------------------------------#*/
{
    TRACE_FUNC;

    // output is generated in the macros because abort/assert signal combination

    // Checks to verify correct behaviour
    TESTSET_ENSURE(a_signal > 0);
    TESTSET_ENSURE(a_signal <= NSIG);
    TESTSET_ENSURE(expected_signal == 0);

    expected_signal = a_signal;
}


/*#----------------------------------------------------------------------#*/
int TestSet_execute()

/*-
 *  Description : Execute all test cases belonging to the test set
 *
 *  Input(s)    : -
 *  Output(s)   : -
 *  Returns     : nr of failed test cases
 *  Notes       : 
 *  Pre.Cond.   : test cases and test sets have to have been build
 *  Post.Cond.  : all test cases are run and test run statistics
 *                have been build
 */

/*#----------------------------------------------------------------------#*/
{
    TRACE_FUNC;

    TestSet* next_test_set;

    TestSet_install_handlers();

    current_test_set = &default_test_set;

    TestSet_output_all_test_sets_before(nr_of_test_sets);
    nr_of_test_sets = 0;

    while ( current_test_set != 0 )
    {
        /* use setjmp to jump back here if anything fails */
        if (setjmp(environment_buffer) == 0)
        {
            TestSet_execute_test_set();
        }
        next_test_set = current_test_set->next;
        *current_test_set = destructed_test_set;
        current_test_set = next_test_set;
    }

    TestSet_output_all_test_sets_after(passed, failed, total, nr_of_test_sets);

    return failed;
}

/* End of Document */
